/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package solitaireofdoom.klondike;

import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.io.*;
import java.util.Vector;

// XML imports
import java.io.File;
import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import solitaireofdoom.solitaire.*;

/**
 * The class Klondike inherits JComponent and displays the piles and cards necessary for playing the klondike solitaire. It saves and loads games, handles checks of whether the game has finished or if the player has any plausible moves (also shows the plausible moves if the user wishes), and various other functions that are specific to the klondike solitaire.
 *
 * @author Administrator
 */
public class Klondike extends SolitaireType
{
    private BufferedImage cards;
    private SolitaireFrame frame;

    private KlondikeDrawPile drawPile;
    private KlondikeDonePile donePile;
    private KlondikeSuitPile[] suitPiles;
    private KlondikePlayPile[] playPiles;

    private final boolean checkPlausibleOutput = false; // Whether the checkPlausibleMoves should show debug information or not
    public final String name = "Klondike";

    public Klondike(SolitaireFrame frame, BufferedImage cards)
    {
        this.cards = cards;
        this.frame = frame;
        
        this.setSize(633, 710);

        int playingPileLeftMargin = 12;

        this.suitPiles = new KlondikeSuitPile[4];
        for (int i = 0; i < this.suitPiles.length; i++)
        {
            this.suitPiles[i] = new KlondikeSuitPile(Card.Suits[i], this, cards);
            this.suitPiles[i].setSize(81, 125);
            this.suitPiles[i].setLocation(playingPileLeftMargin + 270 + i * 80 + i * 10, 0);
            this.add(this.suitPiles[i]);
        }

        this.playPiles = new KlondikePlayPile[7];
        for (int i = 0; i < this.playPiles.length; i++)
        {
            this.playPiles[i] = new KlondikePlayPile(this, cards);
            this.playPiles[i].setSize(81, 605);
            this.playPiles[i].setLocation(playingPileLeftMargin + 80 * i + 10 * i, 135);
            this.add(this.playPiles[i]);
        }
        this.donePile = new KlondikeDonePile(this, cards);
        this.donePile.setSize(81, 125);
        this.donePile.setLocation(102, 0);
        this.add(donePile);

        this.drawPile = new KlondikeDrawPile(this.donePile, this, cards);
        this.drawPile.setSize(93, 125);
        this.drawPile.setLocation(0, 0);
        this.add(drawPile);

        KlondikeMouseHandler mouseHandler = new KlondikeMouseHandler();
        this.addMouseListener(mouseHandler);

        newGame();
    }

    /**
     * Starts a new freecell game
     */
    public void newGame()
    {
        gameTimer.stop();

        for (int i = 0; i < this.suitPiles.length; i++)
            this.suitPiles[i].clear();

        for (int i = 0; i < this.playPiles.length; i++)
            this.playPiles[i].clear();

        this.donePile.clear();
        this.drawPile.clear();

        Deck d = new Deck();
        d.clearPile();
        d.fillPile();
        d.shuffle();

        for (int i = 0; i < this.playPiles.length; i++)
        {
            Deck pileDeck = d.getCards(i + 1, 2);

            for (int j = 0; j < pileDeck.size(); j++)
            {
                CardCanvas cc = new CardCanvas(pileDeck.getCard(j), this.cards);
                cc.pile = this.playPiles[i];
                this.playPiles[i].pileCards.add(cc);
            }
        }

        for (int i = 0; i < d.size(); i++)
        {
            CardCanvas cc = new CardCanvas(d.getCard(i), this.cards);
            cc.pile = this.drawPile;
            this.drawPile.pileCards.add(cc);
        }

        for (int i = 0; i < this.playPiles.length; i++)
        {
            this.playPiles[i].redraw();
        }

        this.drawPile.redraw();
    }

    /**
     * Method moves the cards that can be moved to the suit pile
     */
    public void autoMoveToSuit()
    {
        int moves = 0;

        for (int i = 0; i < this.playPiles.length; i++)
        {
            if (this.playPiles[i].pileCards.size() > 0)
            {
                Card c = this.playPiles[i].pileCards.lastElement().getCard();

                for (int j = 0; j < this.suitPiles.length; j++)
                {
                    Card cAbove = this.suitPiles[j].pileCards.lastElement().getCard();

                    if (cAbove.getSuit() == c.getSuit() &&
                        (cAbove.getRank() + 1 == c.getRank() ||
                        (cAbove.getRank() == 999 && c.getRank() == 1)))
                    {
                        this.suitPiles[j].add(this.playPiles[i].pileCards.lastElement());
                        this.playPiles[i].remove(this.playPiles[i].pileCards.lastElement());

                        moves++;
                    }
                }
            }
        }

        if (donePile.pileCards.size() > 0)
        {
            Card c = this.donePile.pileCards.lastElement().getCard();

            for (int j = 0; j < this.suitPiles.length; j++)
            {
                Card cAbove = this.suitPiles[j].pileCards.lastElement().getCard();

                if (cAbove.getSuit() == c.getSuit() &&
                    (cAbove.getRank() + 1 == c.getRank() ||
                    (cAbove.getRank() == 999 && c.getRank() == 1)))
                {
                    this.suitPiles[j].add(this.donePile.pileCards.lastElement());
                    this.donePile.remove(this.donePile.pileCards.lastElement());

                    moves++;
                }
            }
        }

        if (moves > 0)
            autoMoveToSuit();
        else
        {
            pileChanged(false);
            return;
        }
    }

    /**
     * Reports that a pile was changed
     *
     * @param drawPileFlip was the drapile flipped during the pile change?
     */
    public void pileChanged(boolean drawPileFlip)
    {
        if (!gameTimer.isRunning())
        {
            this.gameTimeSeconds = 0;
            this.gameTimer.start();
        }

        for (int i = 0; i < this.playPiles.length; i++)
        {
            for (int j = 0; j < this.playPiles[i].pileCards.size(); j++)
            {
                this.playPiles[i].pileCards.elementAt(j).hideHint();
            }
        }

        for (int i = 0; i < this.suitPiles.length; i++)
        {
            this.suitPiles[i].pileCards.lastElement().hideHint();
        }

        for (int i = 0; i < this.drawPile.pileCards.size(); i++)
        {
            this.drawPile.pileCards.elementAt(i).hideHint();
        }

        if (this.donePile.pileCards.size() > 0)
            this.donePile.pileCards.lastElement().hideHint();

        if (this.drawPile.pileCards.size() > 0)
            this.drawPile.pileCards.lastElement().hideHint();

        this.checkPlausibleMoves(false);
        this.checkFinished(drawPileFlip);

        if (drawPileFlip)
            this.plausibleMoves = 0;
    }

    /**
     * Check if the game has finished
     */
    public void checkFinished()
    {
        checkFinished(false);
    }

    /**
     * Check if the game has finished
     *
     * @param drawPileFlip identifies whether the draw pile was flipped
     */
    public void checkFinished(boolean drawPileFlip)
    {
        boolean isFinished = true;

        for (int i = 0; i < this.suitPiles.length; i++)
            isFinished = isFinished && this.suitPiles[i].pileCards.size() == 14;

        if (this.plausibleMoves == 0 && drawPileFlip)
            frame.gameFinished(this, -1);
        else if (isFinished)
        {
            this.gameTimer.stop();

            frame.gameFinished(this, this.gameTimeSeconds);
        }
    }

    /**
     * Checks how many moves are possible
     */
    public void checkPlausibleMoves(boolean showHint)
    {
        int origMoves = this.plausibleMoves;

        Vector<CardCanvas> topFaceUpPlayCards = new Vector<CardCanvas>();
        Vector<CardCanvas> bottomFaceUpPlayCards = new Vector<CardCanvas>();

        // int i defined in scope 1
        for (int i = 0; i < this.playPiles.length; i++)
        {
            if (this.playPiles[i].pileCards.size() > 0)
            {
                // If there is a face up card, add it the last one to bottomFaceUpPlayCards
                if (this.playPiles[i].pileCards.lastElement().getCard().getFacing())
                    bottomFaceUpPlayCards.add(this.playPiles[i].pileCards.lastElement());

                    // int j defined in scope 2
                for (int j = 0; j < this.playPiles[i].pileCards.size(); j++)
                {
                    // Found a king but it's on top, with no face down cards
                    if (j == 0 && this.playPiles[i].pileCards.elementAt(j).getCard().getRank() == 13 &&
                        this.playPiles[i].pileCards.elementAt(j).getCard().getFacing())
                        break;
                    // Found a face up card, will break now
                    if (this.playPiles[i].pileCards.elementAt(j).getCard().getFacing())
                    {
                        topFaceUpPlayCards.add(this.playPiles[i].pileCards.elementAt(j));
                        break;
                    }
                }
            }
        }

        Vector<CardCanvas> topSuitCards = new Vector<CardCanvas>();
        for (int i = 0; i < this.suitPiles.length; i++)
        {
            topSuitCards.add(this.suitPiles[i].pileCards.lastElement());
        }

        CardCanvas donePileCard = null;
        if (this.donePile.pileCards.size() > 0)
            donePileCard = this.donePile.pileCards.lastElement();

        if (checkPlausibleOutput)
        {
            System.out.print("Top face up playing cards: ");
            for (int i = 0; i < topFaceUpPlayCards.size(); i++)
            {
                System.out.print(topFaceUpPlayCards.elementAt(i).getCard().toString() + ", ");
            }
            System.out.println("");

            System.out.print("Bottom face up playing cards: ");
            for (int i = 0; i < bottomFaceUpPlayCards.size(); i++)
            {
                System.out.print(bottomFaceUpPlayCards.elementAt(i).getCard().toString() + ", ");
            }
            System.out.println("");
        }

        // Check if a king can be moved to an empty pile
        for (int i = 0; i < topFaceUpPlayCards.size(); i++)
        {
            Card c = topFaceUpPlayCards.elementAt(i).getCard();
            if (c.getRank() == 13)
            {
                for (int j = 0; j < this.playPiles.length; j++)
                {
                    if (this.playPiles[j].pileCards.size() == 0)
                    {
                        if (checkPlausibleOutput)
                            System.out.println("Top play pile -> play pile (" + c.toString() + " -> empty)");

                        if (showHint)
                            topFaceUpPlayCards.elementAt(i).showHint();

                        this.plausibleMoves++;
                    }
                }
            }
        }

        // Check if the top face up card in each play pile can be moved to another pile
        for (int i = 0; i < bottomFaceUpPlayCards.size(); i++)
        {
            Card cAbove = bottomFaceUpPlayCards.elementAt(i).getCard();

            for (int j = 0; j < topFaceUpPlayCards.size(); j++)
            {
                Card c = topFaceUpPlayCards.elementAt(j).getCard();
                
                if (c.getRank() + 1 == cAbove.getRank() &&
                    (((cAbove.getSuit() == 'H' || cAbove.getSuit() == 'D') &&
                    (c.getSuit() == 'S' || c.getSuit() == 'C')) ||
                    ((cAbove.getSuit() == 'S' || cAbove.getSuit() == 'C') &&
                    (c.getSuit() == 'H' || c.getSuit() == 'D'))))
                {
                    if (checkPlausibleOutput)
                        System.out.println("Top play pile -> play pile (" + c.toString() + " -> " + cAbove.toString() + ")");

                    if (showHint)
                        topFaceUpPlayCards.elementAt(j).showHint();

                    this.plausibleMoves++;
                }
            }
        }

        boolean canMoveToSuitPile = false;

        for (int i = 0; i < bottomFaceUpPlayCards.size(); i++)
        {
            Card c = bottomFaceUpPlayCards.elementAt(i).getCard();

            // Check if a last face up play pile card can be moved on to a suit pile
            for (int j = 0; j < topSuitCards.size(); j++)
            {
                Card cSuit = topSuitCards.elementAt(j).getCard();
               
                if (cSuit.getSuit() == c.getSuit() &&
                    (cSuit.getRank() + 1 == c.getRank() ||
                    cSuit.getRank() == 999 && c.getRank() == 1))
                {
                    if (checkPlausibleOutput)
                        System.out.println("Bottom play pile -> suit pile (" + c.toString() + " -> " + cSuit.toString() + ")");

                    if (showHint)
                        bottomFaceUpPlayCards.elementAt(i).showHint();

                    canMoveToSuitPile = true;

                    this.plausibleMoves++;
                }
            }

            // Check if the draw pile card can be moved to a play pile
            if (donePileCard != null)
            {
                Card cDone = donePileCard.getCard();
                if (cDone.getRank() + 1 == c.getRank() &&
                    (((c.getSuit() == 'H' || c.getSuit() == 'D') &&
                    (cDone.getSuit() == 'S' || cDone.getSuit() == 'C')) ||
                    ((c.getSuit() == 'S' || c.getSuit() == 'C') &&
                    (cDone.getSuit() == 'H' || cDone.getSuit() == 'D'))))
                {
                    if (checkPlausibleOutput)
                        System.out.println("Draw pile -> play pile (" + cDone.toString() + " -> " + c.toString() + ")");

                    if (showHint)
                        donePileCard.showHint();

                    this.plausibleMoves++;
                }

                if (cDone.getRank() == 13)
                    for (int j = 0; j < this.playPiles.length; j++)
                        if (this.playPiles[j].pileCards.size() == 0)
                            if (showHint)
                                donePileCard.showHint();
            }
        }

        // Check if the draw pile card can be put on a suit pile
        if (donePileCard != null)
        {
            Card cDone = donePileCard.getCard();
            for (int i = 0; i < topSuitCards.size(); i++)
            {
                Card cSuit = topSuitCards.elementAt(i).getCard();
                if (cSuit.getSuit() == cDone.getSuit() &&
                    (cSuit.getRank() + 1 == cDone.getRank() ||
                    cSuit.getRank() == 999 && cDone.getRank() == 1))
                {
                    if (checkPlausibleOutput)
                        System.out.println("Draw pile -> suit pile (" + cDone.toString() + " -> " + cSuit.toString() + ")");

                    if (showHint)
                        donePileCard.showHint();

                    this.plausibleMoves++;
                }
            }
        }

        if (showHint && this.plausibleMoves == origMoves && (this.drawPile.pileCards.size() > 0 && !(this.drawPile.pileCards.size() > 1 && this.donePile.pileCards.size() == 0)))
            this.drawPile.pileCards.lastElement().showHint();

        if (!canMoveToSuitPile && this.drawPile.pileCards.size() == 1 && this.donePile.pileCards.size() == 0)
            plausibleMoves = 0;

        if (checkPlausibleOutput)
            System.out.println("Plausible moves: " + this.plausibleMoves);
    }

    /**
     * Shows a plausible move to the user
     */
    public void showHint()
    {
        checkPlausibleMoves(true);
    }

    /**
     * Load a saved klondike game
     */
    public void loadGame()
    {
        this.drawPile.pileCards.clear();
        this.donePile.pileCards.clear();
        for (int i = 0; i < this.playPiles.length; i++)
            this.playPiles[i].pileCards.clear();
        for (int i = 0; i < this.suitPiles.length; i++)
            this.suitPiles[i].pileCards.clear();

        File f = new File(System.getProperty("user.dir") + "\\Klondikesave.xml");

        if (f.exists())
        {
            try
            {
                DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
                Document doc = docBuilder.parse(f);

                doc.getDocumentElement().normalize();

                NodeList drawPile = doc.getElementsByTagName("klondikeDrawPile");
                if (drawPile.getLength() > 0)
                {
                    NodeList drawPileCards = drawPile.item(0).getChildNodes();
                    for (int i = 0; i < drawPileCards.getLength(); i++)
                    {
                        Node card = drawPileCards.item(i);
                        if (card.getNodeName().compareTo("card") == 0)
                        {
                            NamedNodeMap attributes = card.getAttributes();
                            if (attributes.getLength() > 0)
                            {
                                CardCanvas cc = new CardCanvas(
                                    new Card(attributes.getNamedItem("suit").getTextContent().charAt(0),
                                        Integer.parseInt(attributes.getNamedItem("rank").getTextContent()),
                                        Boolean.parseBoolean(attributes.getNamedItem("facing").getTextContent())),
                                    cards
                                );

                                this.drawPile.add(cc);
                            }
                        }
                    }
                }

                NodeList donePile = doc.getElementsByTagName("klondikeDonePile");
                if (donePile.getLength() > 0)
                {
                    NodeList donePileCards = donePile.item(0).getChildNodes();
                    for (int i = 0; i < donePileCards.getLength(); i++)
                    {
                        Node card = donePileCards.item(i);
                        if (card.getNodeName().compareTo("card") == 0)
                        {
                            NamedNodeMap attributes = card.getAttributes();
                            if (attributes.getLength() > 0)
                            {
                                CardCanvas cc = new CardCanvas(
                                    new Card(attributes.getNamedItem("suit").getTextContent().charAt(0),
                                        Integer.parseInt(attributes.getNamedItem("rank").getTextContent()),
                                        Boolean.parseBoolean(attributes.getNamedItem("facing").getTextContent())),
                                    cards
                                );

                                this.donePile.add(cc);
                            }
                        }
                    }
                }

                NodeList playPiles = doc.getElementsByTagName("klondikePlayPile");
                for (int p = 0; p < playPiles.getLength(); p++)
                {
                    NodeList playPileCards = playPiles.item(p).getChildNodes();
                    for (int i = 0; i < playPileCards.getLength(); i++)
                    {
                        Node card = playPileCards.item(i);
                        if (card.getNodeName().compareTo("card") == 0)
                        {
                            NamedNodeMap attributes = card.getAttributes();
                            if (attributes.getLength() > 0)
                            {
                                CardCanvas cc = new CardCanvas(
                                    new Card(attributes.getNamedItem("suit").getTextContent().charAt(0),
                                        Integer.parseInt(attributes.getNamedItem("rank").getTextContent()),
                                        Boolean.parseBoolean(attributes.getNamedItem("facing").getTextContent())),
                                    cards
                                );

                                this.playPiles[Integer.parseInt(playPiles.item(p).getAttributes().getNamedItem("column").getTextContent()) - 1].add(cc);
                            }
                        }
                    }
                }

                NodeList suitPiles = doc.getElementsByTagName("klondikeSuitPile");
                for (int p = 0; p < suitPiles.getLength(); p++)
                {
                    NodeList suitPileCards = suitPiles.item(p).getChildNodes();
                    for (int i = 0; i < suitPileCards.getLength(); i++)
                    {
                        Node card = suitPileCards.item(i);
                        if (card.getNodeName().compareTo("card") == 0)
                        {
                            NamedNodeMap attributes = card.getAttributes();
                            if (attributes.getLength() > 0)
                            {
                                CardCanvas cc = new CardCanvas(
                                    new Card(attributes.getNamedItem("suit").getTextContent().charAt(0),
                                        Integer.parseInt(attributes.getNamedItem("rank").getTextContent()),
                                        Boolean.parseBoolean(attributes.getNamedItem("facing").getTextContent())),
                                    cards
                                );

                                char suit = suitPiles.item(p).getAttributes().getNamedItem("suit").getTextContent().charAt(0);
                                this.suitPiles[(suit == 'H' ? 0 : (suit == 'S' ? 1 : (suit == 'D' ? 2 : suit == 'C' ? 3 : -1)))].add(cc);
                            }
                        }
                    }
                }
            }
            catch(Exception e)
            {

            }
        }
        else
        {
            this.newGame();
        }

        this.drawPile.redraw();
        this.donePile.redraw();
        for (int i = 0; i < this.playPiles.length; i++)
            this.playPiles[i].redraw();
        for (int i = 0; i < this.suitPiles.length; i++)
            this.suitPiles[i].redraw();
    }

    /**
     * Save the current klondike game.
     */
    public void saveGame()
    {
        File f = new File(System.getProperty("user.dir") + "\\Klondikesave.xml");
        
        try
        {
            if (!f.exists())
                f.createNewFile();
            
            BufferedWriter writer = new BufferedWriter(new FileWriter(f));

            writer.write("<solitaire type=\"Klondike\">" + "\n");

            writer.write("\t<klondikeDrawPile>" + "\n");
            for (int i = 0; i < this.drawPile.pileCards.size(); i++)
            {
                Card c = this.drawPile.pileCards.elementAt(i).getCard();

                writer.write("\t\t<card suit=\"" + c.getSuit() + "\" rank=\"" + c.getRank() + "\" facing=\"" + c.getFacing() + "\" />"  + "\n");
            }
            writer.write("\t</klondikeDrawPile>" + "\n");

            writer.write("\t<klondikeDonePile>" + "\n");
            for (int i = 0; i < this.donePile.pileCards.size(); i++)
            {
                Card c = this.donePile.pileCards.elementAt(i).getCard();

                writer.write("\t\t<card suit=\"" + c.getSuit() + "\" rank=\"" + c.getRank() + "\" facing=\"" + c.getFacing() + "\" />"  + "\n");
            }
            writer.write("\t</klondikeDonePile>" + "\n");

            for (int p = 0; p < this.playPiles.length; p++)
            {
                writer.write("\t<klondikePlayPile column=\"" + (p + 1) + "\">" + "\n");

                for (int i = 0; i < this.playPiles[p].pileCards.size(); i++)
                {
                    Card c = this.playPiles[p].pileCards.elementAt(i).getCard();

                writer.write("\t\t<card suit=\"" + c.getSuit() + "\" rank=\"" + c.getRank() + "\" facing=\"" + c.getFacing() + "\" />"  + "\n");
                }

                writer.write("\t</klondikePlayPile>" + "\n");
            }

            for (int p = 0; p < this.suitPiles.length; p++)
            {
                writer.write("\t<klondikeSuitPile suit=\"" + (p == 0 ? 'H' : (p == 1 ? 'S' : (p == 2 ? 'D' : (p == 3 ? 'C' : '\0')))) + "\">" + "\n");

                for (int i = 0; i < this.suitPiles[p].pileCards.size(); i++)
                {
                    Card c = this.suitPiles[p].pileCards.elementAt(i).getCard();

                writer.write("\t\t<card suit=\"" + c.getSuit() + "\" rank=\"" + c.getRank() + "\" facing=\"" + c.getFacing() + "\" />"  + "\n");
                }

                writer.write("\t</klondikeSuitPile>" + "\n");
            }

            writer.write("</solitaire>");

            writer.flush();
            writer.close();
        }
        catch (Exception exc)
        {

        }
    }

    /**
     * class to handle the mouse options
     */
    class KlondikeMouseHandler extends MouseAdapter
    {
        /**
         * Checks for mouse click and then performs an action based on the facing of the card.
         *
         * @param e <i>MouseEvent</i>
         */
        public void mouseClicked(MouseEvent e)
        {
            if (e.getButton() == e.BUTTON3)
            {
                autoMoveToSuit(); // this methos is in
            }
        }
    }
}
